/*Name: rsa.cpp
  Last Modification: 31/05/10 23:25
  Description: Ce fichier regroupe toutes les fonctions necessaires au fonctionnement
  de l'algorithme de chiffrement RSA et à son exportation
  License: GNU GPL v3
    Copyright (C) 2012 G. Bonnoron, P.Brunet, C. Duchene et F. Cornevaux-Juignet

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.*/

//inclusion des bibliothèques
#include "rsa.h"

//constructeur de la classe
Rsa::Rsa() : Algorithme()
{
    p=53;
    q=97;
    n=0;
    phi = 0;
    e=0;
    d=0;
    bouton = new QPushButton(QString::fromUtf8("Gestion des clés"));
    cle1 = new QLineEdit(QString::fromUtf8("Entrez votre clé p OU n ici"));
    cle2 = new QLineEdit(QString::fromUtf8("Entrez votre clé q OU e ou d ici"));
    clefichier = new QLineEdit(QString::fromUtf8("ou entrez ici un fichier contenant les clés..."));
    clefichier2 = new QLineEdit(QString::fromUtf8(""));
    crypter = new QTextEdit(QString::fromUtf8("Vos données (dé)chiffrées apparaîtront ici"));
    fenetre = new QWidget();
    label_p = new QLabel();
    label_q = new QLabel();
    label_n = new QLabel();
    label_e = new QLabel();
    label_d = new QLabel();
    label_phi = new QLabel();
    barre = new QProgressBar();
    coche = new QCheckBox(QString::fromUtf8("Sauvegarder la clé"));
    coche2 = new QCheckBox(QString::fromUtf8("J'ai donné n et e ou d comme clés"));
    valeurAJour();
    generercle();
    QObject::connect(bouton, SIGNAL(clicked()), this->fenetre, SLOT(show()));
}

//fonction calculant les variables e, d, n et phi à partir de p et q
void Rsa::calculVar(){
    mpz_class pgcd;
    gmp_randstate_t graine;
    n=p*q;
    phi = (p-1)*(q-1);
    gmp_randinit_default(graine);
    e=2;
    //Pour trouver e, on génère un nombre aléatoire et on le divise par son pgcd avec phi pour qu'il soit premier entre eux
    gmp_randseed_ui(graine, time(NULL));
    mpz_urandomb(e.get_mpz_t(), graine, 10);
    mpz_gcd(pgcd.get_mpz_t(), e.get_mpz_t(), phi.get_mpz_t());
    e = e/pgcd;
    //on utilise ensuite la fonction fournie par GMP pour trouver l'inverse de e par phi
    mpz_invert(d.get_mpz_t(), e.get_mpz_t(), phi.get_mpz_t());
    label_p->setText(QString::fromUtf8("clé p = ") + QString::fromStdString(p.get_str()));
    label_q->setText(QString::fromUtf8("clé q = ") + QString::fromStdString(q.get_str()));
    label_phi->setText(QString::fromUtf8("clé phi = ") + QString::fromStdString(phi.get_str()));
    label_n->setText(QString::fromUtf8("clé n = ") + QString::fromStdString(n.get_str()));
    label_e->setText(QString::fromUtf8("clé e = ") + QString::fromStdString(e.get_str()));
    label_d->setText(QString::fromUtf8("clé d = ") + QString::fromStdString(d.get_str()));
    //fin du calcul des variables.
}

//cette fonction reçoit le plus grand bloc de lettre, sous forme de mpz_class, plus petit que n,
//ainsi il est directement chiffré tel que prévu par RSA
inline mpz_class Rsa::changeLettreChiffre(mpz_class lettre)
{
    mpz_class res;
    mpz_powm(res.get_mpz_t(),lettre.get_mpz_t(),e.get_mpz_t(),n.get_mpz_t());
    return res;
}

//de même, cette fonction permet le déchiffrement à partir d'un bloc de lettres juste inférieur à n
inline mpz_class Rsa::changeLettreDechiffre(mpz_class lettre)
{
    mpz_class res;
    mpz_powm(res.get_mpz_t(),lettre.get_mpz_t(),d.get_mpz_t(),n.get_mpz_t());
    return res;
}

//fonction mettant à jour les clefs si elles ont été modifiées
void Rsa::majClef(){
    //si on a rentré un fichier dans lequel on veut récupérer les clefs p et q, on écrit ces clefs
    //dans les cases correspondantes
    if(clefichier->isModified() && clefichier->text() != "")
    {
        QFile file(clefichier->text());
        file.open(QIODevice::ReadOnly | QIODevice::Truncate);
        cle1->setText(file.readLine());
        cle2->setText(file.readLine());
        p = mpz_class(cle1->text().toStdString());
        q = mpz_class(cle2->text().toStdString());
        QString buff = file.readLine();
        n = mpz_class(buff.toStdString());
        buff = file.readLine();
        e = mpz_class(buff.toStdString());
        buff = file.readLine();
        d = mpz_class(buff.toStdString());
        file.close();
    }
    else{
        // si ce sont des clefs que l'on a rentrées, on remplace celles existantes par les nouvelles
        if(cle1->isModified() || cle2->isModified())
        {
            if(coche2->isChecked())	//si on a coché la case alors c'est des clef n et e sinon c'est p et q
            {
                n=mpz_class((cle1->text()).toStdString());
                e=mpz_class((cle2->text()).toStdString());
            }
            else
            {
                p=mpz_class((cle1->text()).toStdString());
                q=mpz_class((cle2->text()).toStdString());
                calculVar();
                cle1->setText(QString::fromStdString(p.get_str()));
                cle2->setText(QString::fromStdString(q.get_str()));
            }
        }
        //si rien n'est entré, on entre celles générées lors de la création de l'instance rsa
        else
        {
            cle1->setText(QString::fromStdString(p.get_str()));
            cle2->setText(QString::fromStdString(q.get_str()));
        }
    }
}

//cette fonction décompose le message reçu en groupe de lettres le plus grand possible et inférieur
//à n afin de rendre plus de sécurité au système. Ces blocs de lettres sont ensuite chiffré puis affiché
//ou écrit dans un fichier en fonction des données entrées par l'utilisateur
void Rsa::chiffre(bool fich)
{
    QTime mesure;
    mpz_class tp=0;
    mpz_class motChiffre = 0;
    mpz_class tempo;
    int nbZero=0;
    majClef();
    mesure.start();
    if(fich){   //Dans le cas où les données entrées sont dans un fichier
        QByteArray buff;
        long size = line.length();
        barre->setVisible(true);
        lineOut.clear();    //on vide avant d'enregistrer les données de sortie
        for (int i = 0; i<line.length();i++){   //Pour chaque caractére ascii
            tp= tp * (257);     //on fait un décalage des caractères lu précédement
            tp= tp + (unsigned char)line[i] +1;  // et on ajoute le nouveau caractère
            if(tp*(257)+256>n  || i == line.length()-1){   //si on a fini de lire le fichier ou si on dépasse n en ajoutant un caractére supplémentaire
                nbZero = 0;
                motChiffre =  changeLettreChiffre(tp);  //alors on chiffre toutes les données que l'on a lues
                tp=0;   //et on réinitialise tp pour chiffrer la suite du fichier
                buff.clear();   //on vide la mémoire tempon
                mpz_class calc = motChiffre;
                while(calc*256+255<n){
                    calc = calc*256;
                    nbZero++;
                }
                while(motChiffre>0){    //tant qu'il reste des données chiffrées non enregistrées
                    tempo= motChiffre%(256);    //on prend le caractére le plus à droite
                    buff.prepend((signed char)(tempo.get_ui()));//on l'écrit dans le buffer avant ceux déjà écrit
                    motChiffre = motChiffre/(256);//puis on met à jour le motChiffre qu'il reste à écrire dans le fichier
                }
                for(int i=0;i<nbZero;i++)
                    buff.prepend((char)0);
                lineOut.append(buff);   //dès que le mot chiffré est correctement inscrit dans le buffer, on l'écrit à la suite dans le fichier
            }
            barre->setValue(100 * i/size + 1);
            //cerr << "\r[" <<  (100 * i/size) << " %]";  //lorsque le programme est éxecuté en console, on peut voir l'avancement dans le chiffrement du fichier
        }
    }
    else{   //Si on chiffre des données entrées manuellement dans la plate-forme
        messageOut = "";    //on nettoie les données
        QString buff;
        barre->setVisible(false); //on cache la barre, inutile vue la vitesse !
        for (int i = 0; i<messageIn.length();i++){  //pour chaque lettre
            tp= tp * (127-32);  //on fait un décalage des caractéres déjà enregistré
            tp= tp + messageIn[i].toAscii()-32+1 ; //puis on ajoute le nouveau caractére
            if(tp*(127-32)+127-32>=n  || i == messageIn.length()-1){    //Si on arrive à la fin du fichier ou si en ajoutant un caractère on risque de dépasser n
                nbZero = 0;
                motChiffre =  changeLettreChiffre(tp);  //on chiffre les données déjà lues
                tp=0;
                buff ="";
                mpz_class calc = motChiffre;
                while(calc*95+95<n){
                    calc = calc*95;
                    nbZero++;
                }
                while(motChiffre>0){    //tant qu'il reste des données chiffrées non enregistrées
                    tempo= motChiffre%(127-32);    //on prend le caractére le plus à droite
                    buff = QChar((char)(tempo.get_ui()+32)) + buff; //et on l'écrit juste avant les données déjà enregistrées dans la mémoire tampon
                    motChiffre = motChiffre/(127-32); //Puis on met à jour les données restantes à enregistrer
                }
                for(int i=0;i<nbZero;i++)
                    buff = QChar(0) + buff;
                messageOut += buff; //puis on ajoute les données enregistrées à la suite de ce qui a déjà été inscrit
            }
        }
    }
    this->outRsa(fich); //On sort les résultats
    temps = mesure.elapsed();
}

//Cette fonction fonctionne de la même façon que chiffrer mais pour dechiffrer
void Rsa::dechiffre(bool fich)
{
    QTime mesure;
    mpz_class tp=0;
    mpz_class motChiffre = 0;
    long size = line.length();
    mpz_class tempo;
    mpz_class pow;
    majClef();
    mesure.start();
    if(fich){   //si on travaille sur un fichier
        barre->setVisible(true);
        QByteArray buff;
        pow = 1;
        lineOut.clear();    //on vide le fichier de sortie
        for (int i = 0; i<line.length();i++){   //pour chaque caractère
            tp *= 256;  //on décale les données déjà lues
            tp+=(unsigned char)line[i];   // puis on ajoute le nouveau caractère
            if((unsigned char)line[i]==0 && tp==0)
                pow *=256;
            if((tp*256+255)*pow>=n || i==line.length()-1){    //si on est à la fin du fichier ou qu'en ajoutant un nouveau caractére, on dépasse n
                pow = 1;
                motChiffre =  changeLettreDechiffre(tp);    //on chiffre les données déjà lues
                tp=0;   //on remet à 0 les données stockées
                buff.clear();   //on vide le fichier de sortie du message chiffré
                while(motChiffre>0){    //tant qu'il reste des données à sortir
                    tempo= (motChiffre-1)%(257);    //on récupére le caractère le plus à droite auquel on enleve 1 pour le ramener entre 0 et 255
                    buff.prepend((signed char)(tempo.get_ui()));// puis on l'ajoute juste avant les autres caractères
                    motChiffre = motChiffre/(257);  // et on met à jour les données à dechiffrer restantes
                }
                lineOut.append(buff);   // on ajoute ce bloc de caractère déchiffré à la suite du fichier
            }
            barre->setValue(100 * i/size + 1);
            //cerr << "\r[" <<  (100 * i/size) << " %]";  //On permet aux personnes exécutant le programme en console de voir l'avancement du déchiffrement
        }
    }
    else{
        barre->setVisible(false);
        messageOut = "";
        QString buff;
        pow = 1;
        for (int i = 0; i<messageIn.length();i++){
            tp *= (127-32);//on décale les caractères déjà lu
            tp+=messageIn[i].toAscii()-32; ;  //puis on ajoute le nouveau caractère
            if(messageIn[i].toAscii()==0)
                pow*=(127-32);
            if((tp*(127-32)+127-32)*pow>=n || i==messageIn.length()-1){       //si on arrive à la fin du fichier ou qu'en ajoutant un caractère, les données à déchiffrer dépassent n
               pow = 1;
                motChiffre =  changeLettreDechiffre(tp);    // on déchiffre les caractères lus
                tp=0;   //et on remet à 0 les données lues à dechiffrer
                buff ="";
                while(motChiffre>0){
                    tempo= (motChiffre-1)%(127-32); //on récupère le caractère le plus à droite et on le remet entre 0 et 95
                    buff = QChar((char)(tempo.get_ui()+32)) + buff;   //on l'ajoute avant les caractères du bloc déjà écrit
                    motChiffre = motChiffre/(127-32); //puis on met à jour les données restantes à écrire
                }
                messageOut += buff; //On ajoute le bloc déchiffré à la suite du message de sortie
            }
        }
    }
    this->outRsa(fich); //On affiche le résultat ou on l'écrit dans un fichier
    temps = mesure.elapsed();
}

//cette fonction premet de rendre le résultat dans un fichier ou dans la plate-forme en fonction des données entrées
void Rsa::outRsa(bool fichier)
{
    if(fichier){    //si les données entrées sont dans un fichier, on ressort un fichier
        if(fileOut.isEmpty())   //qui aura le nom choisi par l'utilisateur
            fileOut = "out_RSA.txt";    //ou un nom prédéfini
        QFile file(fileOut);
        if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
            crypter->setText(QString("Erreur lors du l'écriture fichier"));
        else{
            file.write(lineOut);
            crypter->setText(QString("sortie dans " + fileOut));    //On avertit l'utilisateur que tout est fini et le nom du fichier
        }
    }
    else{   //Si les données ont directement été entrées dans la plate-forme
        crypter->setText(messageOut);   //On affiche le résultat dans la plate-forme
    }
}

//On affiche les différents cadres pour le retour d'informations et pour la gestion des clefs
QList<QWidget *> Rsa::affiche()
{
    QList<QWidget* > liste = QList<QWidget *>();
    QLabel* texte = new QLabel(QString::fromUtf8("<h1> Chiffre de RSA :</h1>"));
    liste.append(texte);
    bouton->maximumSize();
    liste.append(bouton);
    liste.append(cle1);
    liste.append(cle2);
    liste.append(clefichier);
    liste.append(coche2);
    liste.append(barre);
    liste.append(crypter);
    barre->setVisible(false);
    return liste;
}

//Destructeur de la classe RSA
Rsa::~Rsa()
{
}

//fonction permetant l'export de l'algorithme en bibliothèques dynamique
extern "C" MY_EXPORT Algorithme* maker()
{
    return new Rsa();
}

//fonction permettant de générer des clefs automatiquement
void Rsa::generercle()
{
    QVBoxLayout *layout = new QVBoxLayout();
    layout->addWidget(new QLabel(QString::fromUtf8("Uniquement pour le calcul, ne pas dévoiler ! ")));
    layout->addWidget(label_p);
    layout->addWidget(label_q);
    layout->addWidget(label_phi);
    layout->addWidget(new QLabel(QString::fromUtf8("")));
    layout->addWidget(new QLabel(QString::fromUtf8("Ceci est votre clé privée : ")));
    layout->addWidget(label_d);
    layout->addWidget(new QLabel(QString::fromUtf8("")));
    layout->addWidget(new QLabel(QString::fromUtf8("Ceci est la clé publique : ")));
    layout->addWidget(label_n);
    layout->addWidget(label_e);
    layout->addWidget(new QLabel(QString::fromUtf8("")));
    layout->addWidget(new QLabel(QString::fromUtf8("Inutile de rentrer les clés, ce module s'en est chargé !")));
    QHBoxLayout * layout2 = new QHBoxLayout();
    layout->addWidget(coche);
    layout2->addWidget(new QLabel("Entrez un nom de fichier dans lequel vous enregistrerez : "));
    layout2->addWidget(clefichier2);
    layout->addLayout(layout2);
    QHBoxLayout * layout3 = new QHBoxLayout();
    QWidget * button = new QPushButton(QString::fromUtf8("C'est noté !"));
    layout3->addWidget(new QLabel(""));
    layout3->addWidget(button);
    QWidget * button2 = new QPushButton(QString::fromUtf8("Nouvelles clefs"));
    layout3->addWidget(new QLabel(""));
    layout3->addWidget(button2);
    layout3->addWidget(new QLabel(""));
    layout->addLayout(layout3);
    fenetre->setLayout(layout);
    fenetre->setWindowTitle(QString::fromUtf8("Clés R.S.A."));
    fenetre->setWindowIcon(QIcon("q4.ico"));
    QObject::connect(button, SIGNAL(pressed()), this->fenetre, SLOT(close()));
    QObject::connect(button, SIGNAL(pressed()), this, SLOT(save()));
    QObject::connect(button2, SIGNAL(pressed()), this, SLOT(valeurAJour()));
    QObject::connect(button, SIGNAL(pressed()), this, SLOT(majClef()));
}

//fonction permettant de générer des clefs
void Rsa::valeurAJour(){
    gmp_randstate_t etat;
    gmp_randinit_default(etat);
    gmp_randseed_ui(etat, time(NULL));
    mpz_urandomb(p.get_mpz_t(), etat, 100); //On génère un grand nombre aléatoire
    gmp_randseed_ui(etat, 8*time(NULL));
    mpz_urandomb(q.get_mpz_t(), etat, 100); //Puis un second
    mpz_nextprime(p.get_mpz_t(), p.get_mpz_t());//On va au premier nombre premier suivant ce nombre
    mpz_nextprime(q.get_mpz_t(), q.get_mpz_t());    // de même pour le deuxième nombre
    this->calculVar();  // On met à jour les différentes variables de l'algorithme
}

//fonction retournant le nom de l'algorithme
QString Rsa::nom()
{
    return "R.S.A.";
}

//fonction fonrnissant une aide pour l'utilisation et le fonctionnement de l'algorithme
QString Rsa::aide()
{
    QString sortie;
    sortie.append(QString::fromUtf8("<h1>Cryptosystème de R.S.A.</h1>"));
    sortie.append(QString::fromUtf8("<h2>Le principe</h2><div align = 'justify'> Du nom de ses trois auteurs Rivest, Shamir et Adleman, cet algorithme à clé asymétrique est un des plus utilisé aujourd'hui. Il repose sur l'arithmétique des nombres premiers."));
    sortie.append(QString::fromUtf8("<p>1- soit p et q deux grands (là réside tout le problème) nombres premiers</p><p>2- on note n = pq</p><p>3- soit phi = phi(n) = (p-1)(q-1) où la fonction phi est l'indicatrice d'Euler.</p><p>4- on choisit e premier avec phi</p><p>5- avec 4- et le théorème de Bézout, il existe d tel que ed = 1 [phi]. "));
    sortie.append(QString::fromUtf8("Les clés peuvent maintenant être distribuées : la clé publique, qui servira au chiffrement des données, est (n, e) ; la clé privée, conservée pour le déchiffrement, est (n, d)."));
    sortie.append(QString::fromUtf8("<p>Pour le chiffrement, ces étapes sont à respecter : <p>1- soit m notre message, on le scinde en sous-messages mi tels que mi < n </p><p>2- on pose ci = mi^e [n]</p>On envoie enfin le message c = c1c2 ... ck</p>"));
    sortie.append(QString::fromUtf8("<p>Une fois le message reçu, il suffit de reprendre ces mêmes étapes en utilisant d au lieu de e. En effet, ci^d = (mi^e)^d = (mi)^(ed) = (mi)^(1 + k * phi) = (mi)(mi)^(k*phi) = mi car d'après le théorème d'Euler, (mi)^(k*phi) = 1 [n]. CQFD.</p>"));
    sortie.append(QString::fromUtf8("La force de l'algorithme réside dans le fait qu'il est très difficile de factoriser les grands nombres en produit de nombres premiers. Cela induit un temps de calcul qui croit exponentiellement, la force de calcul nécessaire au décryptage n'est pas forcément à la mesure de l'importance des données chiffrées.</div>"));
    sortie.append(QString::fromUtf8("<h2>L'utiliser dans le cadre de la plate-forme</h2><div align = 'justify'> Lorsque vous choisissez cet algorithme, une clé est automatiquement générée. Vous pouvez la consulter en cliquant sur gestion des clés. Vous pouvez enregistrer cette clé dans un fichier. Vous pouvez donner un nom ou pas, le fichier par défaut étant clés_RSA. "));
    sortie.append(QString::fromUtf8("Si vous souhaitez utiliser la clé générée par le module, il est inutile de les rentrer. Ne touchez pas aux champs, ils seront remplis automatiquement au moment du chiffrement / déchiffrement. "));
    sortie.append(QString::fromUtf8("En revanche, si vous souhaitez utiliser votre propre clé, prenez garde aux clés que vous possédez ! Si vous possédez p et q, entrez les dans les champs prévus. Si vous n'avez pas cette chance, entrez n et e si vous chiffrer ou d sinon mais surtout, n'oubliez pas de cocher la case \"j'ai donné n et e ou d\" ! "));
    sortie.append(QString::fromUtf8("Enfin, si vous avez un fichier de clés, vous pouvez entrer le nom de ce fichier dans le champ adéquat sans toucher aux autres champs. L'algorithme se charge de faire tout tout seul. <strong>Attention ! </strong>Le chiffrement avec RSA est très coûteux ! Le chiffrement d'une simple image peut prendre une demi-heure ! Ceci dit, profitez bien de la puissance de cet algorithme...</div>"));
    return sortie;
}

//fonction permet de sauvegarder les clefs créées par le programme
void Rsa::save()
{
    if(coche->isChecked())
    {
        QString fichier = clefichier2->text();
        if(fichier == "" )
            fichier = "cles_RSA";
        QFile file(fichier);
        file.open(QIODevice::WriteOnly | QIODevice::Truncate);
        QByteArray line;
        line.append(QString::fromStdString(p.get_str() + '\n'));
        line.append(QString::fromStdString(q.get_str() + '\n'));
        line.append(QString::fromStdString(n.get_str() + '\n' + e.get_str() + '\n' + d.get_str()));
        file.write(line);
        file.close();
    }
}
